package com.fushenlan.epidemicControl.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fushenlan.commons.ApiGlobalErrorDefinition;
import com.fushenlan.commons.GeneralResponse;
import com.fushenlan.epidemicControl.config.JwtProvider;
import com.fushenlan.epidemicControl.config.WxConfig;
import com.fushenlan.epidemicControl.dto.WxReqDTO;
import com.fushenlan.epidemicControl.dto.WxRespDTO;
import com.fushenlan.epidemicControl.dto.WxUserDTO;
import com.fushenlan.epidemicControl.dto.WxUserPhoneInfo;
import com.fushenlan.epidemicControl.service.UserService;
import com.fushenlan.epidemicControl.sms.EncryptUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Administrator
 * @date 2022/4/20 15:18
 */
@Api(tags = "微信授权登录接口")
@Slf4j
@RestController
@RequestMapping("/wx")
public class LoginController {

    @Autowired
    private WxConfig wxConfig;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public static final String WX_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={APPID}&secret={SECRET}&js_code={JSCODE}&grant_type=authorization_code";
    public static final String PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={ACCESS_TOKEN}";
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APPID}&secret={SECRET}";

    public static final String WX_SESSION_ID = "wx_session_id:";
    public static final String WX_SESSION_SALT = "IamSalt88!!!";

    public static final String JWT_SIGN_KEY = "ToBeOrNotToBeThatIsaQuestion";

    @Autowired
    private UserService userService;
    @Autowired
    private JwtProvider jwtProvider;

    @Autowired
    private EncryptUtil encryptUtil;

    /**
     * 获取第三session
     *
     * @param code 调用wx.login()时获取的code 每个code只能使用一次，code的有效期为5min
     * @return session
     */
    @GetMapping("/getThirdSession/{code}")
    public GeneralResponse<?> getThirdSession(@PathVariable("code") String code) {
        //验证code
        if (StrUtil.isBlank(code)) {
            log.info("请求code为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "wechat code2session's code is empty!");
        }
        //替换url变量,请求微信
        String url = WX_URL.replace("{APPID}", encryptUtil.decrypt(wxConfig.getAppid())).replace("{SECRET}", encryptUtil.decrypt(wxConfig.getSecret())).replace("{JSCODE}", code);
        log.info("请求获取微信登录url：{}", url);
        String resp = HttpUtil.get(url);
        log.info("请求获取微信登录结果：{}", resp);
        //解析判断是否响应成功
        WxRespDTO wxRespDTO = JSONUtil.toBean(resp, WxRespDTO.class);
        if (StrUtil.isNotBlank(wxRespDTO.getErrcode()) && !"0".equals(wxRespDTO.getErrcode())) {
            log.info("微信请求登录结果失败,{}", wxRespDTO.getErrmsg());
            return GeneralResponse.fail(wxRespDTO.getErrcode(), wxRespDTO.getErrmsg(), "微信请求登录结果失败");
        }
        if (StrUtil.isBlank(wxRespDTO.getOpenid())) {
            log.info("登录失败，openid为空");
            return GeneralResponse.fail(wxRespDTO.getErrcode(), wxRespDTO.getErrmsg(), "登录失败，openid为空");
        }
        //查询用户是否已经注册过，根据openid查询居民表
        Map<String, Object> userMap = userService.queryInfoByOpenid(wxRespDTO.getOpenid());
        if (CollUtil.isNotEmpty(userMap)) {
            //在这里下发token
            Map<String, Object> payLoad = new HashMap<>();
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            payLoad.put("uid", uuid);
            payLoad.put("openid", wxRespDTO.getOpenid());
            payLoad.put("userId", userMap.get("inhabitantId"));
            String token = jwtProvider.createToken(payLoad, wxRespDTO.getOpenid());
            Map<String, Object> resultMap = MapUtil.of("userInfo", userMap);
            resultMap.put("access_token", token);
            resultMap.put("registerStatus", "1");
            resultMap.put("uuid", uuid);
            redisTemplate.opsForValue().set(WX_SESSION_ID + wxRespDTO.getOpenid() + uuid, resp, 1L, TimeUnit.HOURS);
            return GeneralResponse.success(resultMap);
        }
        Map<String, String> resultMap = MapUtil.of("openid", wxRespDTO.getOpenid());
        resultMap.put("registerStatus", "0");
        String uuid = UUID.randomUUID().toString();
        resultMap.put("uuid", uuid);
        redisTemplate.opsForValue().set(WX_SESSION_ID + wxRespDTO.getOpenid() + uuid, resp, 1L, TimeUnit.HOURS);
        return GeneralResponse.success(resultMap);
    }

    /**
     * 授权登录微信
     *
     * @param wxReqDTO 请求参数
     * @return 授权用户信息
     */
    @RequestMapping("/authLogin")
    public GeneralResponse<?> wxLogin(@RequestBody WxReqDTO wxReqDTO) {
        if (StrUtil.isBlank(wxReqDTO.getOpenid())) {
            log.info("请求参数openid不能为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "openid is empty!");
        }
        String uuid = wxReqDTO.getThirdSession();
        if (StrUtil.isBlank(uuid)) {
            log.info("请求参数thirdSession不能为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "getSession() interface response uuid is empty!");
        }
        if (StrUtil.isBlank(wxReqDTO.getEncryptedData())) {
            log.info("请求参数encryptedData不能为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "encryptedData is empty!");
        }
        if (StrUtil.isBlank(wxReqDTO.getIv())) {
            log.info("请求参数iv不能为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "iv is empty!");
        }
        //redis中获取session_key
        String json = redisTemplate.opsForValue().get(WX_SESSION_ID + wxReqDTO.getOpenid() + uuid);
        log.info("之前存储在redis中的信息：{}", json);
        if (StrUtil.isBlank(json)) {
            log.info("未获取到redis中存储的用户信息，redis_key:{}", WX_SESSION_ID + wxReqDTO.getOpenid() + uuid);
            return GeneralResponse.fail("-1", "登录失败，请重试", "未获取到服务器端存储的用户信息session_key");
        }
        //解析
        WxRespDTO wxRespDTO = JSONUtil.toBean(json, WxRespDTO.class);
        String decrypt = "";
        try {
            decrypt = wxDecrypt(wxReqDTO.getEncryptedData(), wxRespDTO.getSession_key(), wxReqDTO.getIv());
        } catch (Exception e) {
            log.error("用户信息解密失败", e);
            return GeneralResponse.fail("-1", "登录失败", "用户信息解密失败");
        }
        WxUserDTO wxUserDTO = JSONUtil.toBean(decrypt, WxUserDTO.class);
        String sex = "未知";
        if (Integer.parseInt(wxUserDTO.getGender()) == 1) {
            sex = "男";
        }
        if (Integer.parseInt(wxUserDTO.getGender()) == 2) {
            sex = "女";
        }
        wxUserDTO.setGender(sex);
        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("registered", 2);
        hashMap.put("registerStatus", "0");
        hashMap.put("wxUserInfo", wxUserDTO);
        return GeneralResponse.success(hashMap);
    }

    /**
     * 获取用户手机号
     *
     * @param code 动态令牌 注意：getPhoneNumber 返回的 code 与 wx.login 返回的 code 作用是不一样的，不能混用,每个code只能使用一次，code的有效期为5min
     * @return 用户手机号
     */
    @GetMapping("/getUserPhoneNumber/{code}")
    public GeneralResponse<WxUserPhoneInfo> getUserPhoneNumber(@PathVariable("code") String code) {
        //验证code
        if (StrUtil.isBlank(code)) {
            log.info("请求code为空");
            return GeneralResponse.fail(ApiGlobalErrorDefinition.ERROR_REQUEST_BLANK, "code is empty!");
        }
        //先获取access_token
        //替换url变量,请求获取微信凭证
        String url = ACCESS_TOKEN_URL.replace("{APPID}", encryptUtil.decrypt(wxConfig.getAppid())).replace("{SECRET}", encryptUtil.decrypt(wxConfig.getSecret()));
        log.info("请求微信获取access_token的url：{}", url);
        String resp = HttpUtil.get(url);
        log.info("请求微信获取access_token结果：{}", resp);
        JSONObject jsonObject = JSONUtil.parseObj(resp);
        if (StrUtil.isNotBlank(jsonObject.getStr("errcode")) && !"0".equals(jsonObject.getStr("errcode"))) {
            log.info("获取access_token失败");
            return GeneralResponse.fail(jsonObject.getStr("errcode"), jsonObject.getStr("errmsg"), "获取access_token失败");
        }
        String expiresIn = jsonObject.getStr("expires_in");
        String accessToken = jsonObject.getStr("access_token");
        if (Integer.parseInt(expiresIn) == 0) {
            log.info("access_token凭证已过期");
            return GeneralResponse.fail("-1", "获取手机号失败，凭证已过期", "access_token凭证已过期");
        }
        //调用微信获取手机号接口
        String phoneUrl = PHONE_URL.replace("{ACCESS_TOKEN}", accessToken);
        log.info("请求获取微信用户手机号接口响应结果的url：{}", phoneUrl);
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("code", code);
        Object postResp = HttpUtil.post(phoneUrl, JSONUtil.toJsonStr(paramMap));
        log.info("请求获取微信用户手机号接口响应结果：{}", postResp);
        JSONObject jsonObj = JSONUtil.parseObj(postResp);
        if (Integer.parseInt(jsonObj.getStr("errcode")) != 0) {
            log.info("获取微信手机号失败");
            return GeneralResponse.fail(jsonObj.getStr("errcode"), jsonObj.getStr("errmsg"), "获取微信手机号失败");
        }
        WxUserPhoneInfo userPhoneInfo = JSONUtil.toBean(jsonObj.getStr("phone_info"), WxUserPhoneInfo.class, true);
        return GeneralResponse.success(userPhoneInfo);
    }

    /**
     * 解密用户信息
     *
     * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据，详见 用户数据的签名验证和加解密
     * @param sessionKey    对话key
     * @param iv            加密算法的初始向量，详见 用户数据的签名验证和加解密
     * @return 用户解密信息
     * @throws Exception
     */
    private String wxDecrypt(String encryptedData, String sessionKey, String iv) throws Exception {
        byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);
        byte[] ivByte = cn.hutool.core.codec.Base64.decode(iv);
        byte[] key = cn.hutool.core.codec.Base64.decode(sessionKey);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivByte);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(encData), StandardCharsets.UTF_8);
    }
}
